<?php

abstract class IDbRecord extends IObject implements ArrayAccess {
	private $type;
	private $action;
	protected $__deleted = false;
	protected $__saved = false;
	/**
	 * Enter description here...
	 *
	 * @var IDAO
	 */
	private $dao;
	protected $__original = array();
	
	const HAS_ONE = "hasOne";
	const HAS_MANY = "hasMany";
	const BELONGS_TO = "belongsTo";
	
	private $errors = array();
	
	private $hasErrors = false;

	public final function __construct(array $attrs = null) {
		$this->type = get_class($this);
		$this->dao = IDAO::dao($this->type);
		if ($attrs != null) {
			$this->setAttrs($attrs);
		}
		$this->_init();
	}
	
	public function _init() {
		
	}
	
	public function _before_create() {}
	
	public function _after_create() {}
	
	public function _before_read() {}
	
	public function _after_read() {}
	
	public function _before_update() {}
	
	public function _after_update() {}
	
	public function _before_delete() {}
	
	public function _after_delete() {}
	
	public function _before_validate() {}
	
	public function _after_validate() {}
	
	public function action() {
		return $this->action;
	}
	
	public function setAction($action) {
		$this->action = $action;
	}
	
	/**
	 * Enter description here...
	 * 
	 * 返回影响的行数
	 *
	 * @param unknown_type $deep
	 * @return integer
	 */
	public function delete($deep = false) {
		$this->action = "delete";
		
		$this->_before_delete();
		
		//开始删除
		$count = $this->dao->delete($this, $deep);
		$this->__deleted = true;

		$this->_after_delete();
		
		return $count;
	}
	
	public function isDeleted() {
		return $this->__deleted;
	}
	
	public function setDeleted($deleted = true) {
		$this->__deleted = $deleted;
	}
	
	public function asArray() {
		return $this->attrs(true);
	}
	
	public function asJSON() {
		$json = new IJSON($this->attrs());
		return $json->__toString();
	}
	
	public function attr($name) {
		$getter = "_get" . $name;
		if (method_exists($this, $getter)) {
			return $this->$getter();
		}
		$vars = get_object_vars($this);
		return isset($vars[$name])?$vars[$name]:null;
	}
	
	/**
	 * Enter description here...
	 *
	 * @param unknown_type $all 是否包括外键关联的属性
	 */
	public function attrs($all = true) {
		$attrs = array();
		foreach ($this->dao->columns() as $column) {
			$attrs[$column] = $this->attr($column);
		}
		if ($all) {
			foreach ($this->dao->relations() as $relation) {
				$attrs[$relation["attr"]] = $this->attr($relation["attr"]);
			}
		}
		return $attrs;
	}
	
	public function setAttr($name, $value) {
		if (!is_string($name)) {
			return;
		}
		$setter = "_set" . $name;
		if (method_exists($this, $setter)) {
			$this->$setter($value);
			return;
		}
		$this->$name = $value;
	}
	
	public function setAttrs(array $attrs) {
		foreach ($attrs as $name=>$value) {
			$this->setAttr($name, $value);
		}
	}
	
	/**
	 * Enter description here...
	 *
	 * @return IDAO
	 */
	public function dao() {
		return $this->dao;
	}
	
	public function isDirty() {
		return $this->attrs(false) != $this->original();
	}
	
	public function isSaved() {
		return $this->__saved;
	}
	
	public function setSaved($saved = true) {
		$this->__saved = $saved;
	}
	
	public function setType($type) {
		$this->type = $type;
	}
	
	/**
	 * Enter description here...
	 *
	 * @return mixed
	 */
	public function pk() {
		return $this->attr($this->dao->pkName());
	}
	
	public function save(array $attrs = null) {	
		$this->action = $this->isSaved()?"update":"create";
		
		if (is_array($attrs)) {
			$this->setAttrs($attrs);
		}
		
		if ($this->isSaved()) {
			$this->_before_update();
			$this->validate();
			$this->dao->save($this);
			$this->_after_update();
		}
		else {
			$this->_before_create();
			$this->validate();
			$this->dao->save($this);
			$this->_after_create();
		}
	}
	
	public function type() {
		return $this->type;
	}
	
	public function __get($name) {
		return $this->attr($name);
	}
	
	public function __set($name, $value) {
		return $this->setAttr($name, $value);
	}
	
	public function __sleep() {
		return array_keys(get_object_vars($this));
	}
	
	public function __wakeup() {
		$this->__construct();
	}
	
	public function setOriginal(array $original) {
		foreach ($this->dao->columns() as $column) {
			if (isset($original[$column])) {
				$this->__original[$column] = $original[$column];
			}
			else {
				$this->__original[$column] = null;
			}
		}
	}
	
	public function original() {
		return $this->__original;
	}
	
	/**
	 * 判断一个偏移量是否存在
	 *
	 * 实现了 ArrayAccess 接口对应方法
	 * 
	 * @param integer $index
	 * @return boolean
	 */
	public function offsetExists($index) {
		$attrs = $this->attrs();
		return isset($attrs[$index]);
	}
	
	/**
	 * 从一个偏移量中取得数据
	 * 
	 * 实现了 ArrayAccess 接口对应方法
	 *
	 * @param integer $index 偏移量
	 * @return mixed
	 */
	public function offsetGet($index) {
		$attrs = $this->attrs();
		return isset($attrs[$index])?$attrs[$index]:null;
	}
	
	/**
	 * 设置偏移量位置上的值
	 *
	 * 实现了 ArrayAccess 接口对应方法
	 * 
	 * @param integer $index 偏移量
	 * @param mixed $item 值
	 */
	public function offsetSet($index, $item) {
		$this->setAttr($index, $item);
	}
	
	/**
	 * 删除偏移量位置对应的元素
	 * 
	 * 实现了 ArrayAccess 接口对应方法
	 *
	 * @param integer $index 偏移量
	 */
	public function offsetUnset($index) {
		$this->setAttr($index, null);
	}		
	
	public function has($attr) {
		return isset($this[$attr]);
	}
	
	public function errors($attr = null) {
		if ($attr != null) {
			return isset($this->errors[$attr])?$this->errors[$attr]:array();
		}
		return $this->errors;
	}
	
	public function hasErrors() {
		return $this->hasErrors;
	}
	
	public function validate() {
		$this->_before_validate();
		if (IApplication::hasModule("validator")) {
			foreach ($this->dao->validates() as $attr=>$validators) {
				$validator = new IValidator($attr, $this->attr($attr));
				foreach ($validators as $ruleName => $opts) {
					if (!is_array($opts)) {
						$opts = array($opts);
					}
					if (!isset($opts["on"]) || $opts["on"] == $this->action) {
						if (is_string($ruleName)) {
							array_unshift($opts, $ruleName);
						}
						//处理自定义规则
						if (isset($opts[0]) && is_array($opts[0])) {
							$callback = $opts[0];
							if (isset($callback[0]) && $callback[0] == "self") {
								$callback[0] = $this;
							}
							$opts[0] = $callback;
						}
						$validator->applyOpts($opts);
					}
				}
				$errors = $validator->errors();
				if (!empty($errors)) {
					$this->hasErrors = true;
					$this->errors[$attr] = $errors;
				}
				$this->errors[$attr] = $errors;
			}
			if ($this->hasErrors) {
				throw new IValidateException("record validate error");
			}
		}
		$this->_after_validate();
	}
}

?>